package com.jsbs.iam.ident.service.impl;
import com.jsbs.iam.common.core.dto.Result;
import com.jsbs.iam.common.core.utils.RequestUtils;
import com.jsbs.iam.common.redis.service.RedisUtils;
import com.jsbs.iam.ident.auth.SessionInfo;
import com.jsbs.iam.ident.auth.TokenInfo;
import com.jsbs.iam.ident.constant.Constants;
import com.jsbs.iam.ident.dto.IdentApplyTypeCodeDto;
import com.jsbs.iam.ident.entity.IamAccountBase;
import com.jsbs.iam.ident.entity.IamExceptionLoginStrategy;
import com.jsbs.iam.ident.entity.IamLoginLog;
import com.jsbs.iam.ident.feign.IdentFeignInter;
import com.jsbs.iam.ident.mapper.IamExceptionLoginStrategyMapper;
import com.jsbs.iam.ident.mapper.IdentityMapper;
import com.jsbs.iam.ident.service.ILoginMangerService;
import com.jsbs.iam.ident.service.IamUserLogService;
import com.jsbs.iam.ident.service.IdentityService;
import com.jsbs.iam.ident.utils.TokenHelper;
import com.jsbs.iam.ident.vo.IamAccountBaseVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public  class LoginMangerServiceImpl implements ILoginMangerService {

    @Autowired
    private IdentityService identityService;

    @Autowired
    private IamExceptionLoginStrategyMapper iamExceptionLoginStrategyMapper;

    @Autowired
    private IdentFeignInter identFeignInter;

    @Autowired
    private IdentityMapper identityMapper;

    @Autowired
    private IamUserLogService userLogService;

    /**
     * 手机号长度
     */
    private static final Integer length = 11;

    /**
     * 手机号第一位
     */
    private static final String PH = "1";

    private static final String HEADER_TOKEN = "x-token";

    private static final String REDIS_PREFIX = "SESSION";

    private static final String ERROR_TIME = "TIME";

    @Value("${_jwt.secret}")
    private String jwtSecret;

    @Value("${_env.name}")
    private String envName;

    @Override
    public Result login(String account, String passWord,HttpServletRequest request){
        IamAccountBase iamAccountBase = new IamAccountBase();
        IamAccountBase accountBase = null;
        //去除空格
        String trim = account.trim();
        String accountKey = getAccountKey(account);
        String userType = request.getHeader(Constants.X_USER_TYPE);
        if (StringUtils.isNotEmpty(account)){
            IdentApplyTypeCodeDto identApplyTypeCodeDto = new IdentApplyTypeCodeDto();
            Date exceptionLoginStartTime = null;
            Date exceptionLoginEndTime = null;
            Boolean isExceptionLoginTime = false;
            if (!Constants.USER_TYPE_ADMIN.equals(userType)) {
                String applyUrl = request.getHeader(Constants.APPLY_URL);
                identApplyTypeCodeDto.setApplyUrl(applyUrl);
                Result result = identFeignInter.queryApplyTypeCode(identApplyTypeCodeDto);
                Object data = result.getData();
                String applyTypeCode = null;
                if (data != null) {
                    applyTypeCode = data.toString();
                } else {
                    throw new RuntimeException("根据Url查询到的应用类型为空，请检查url是否正确");
                }

                log.info("根据前端url：{}，查询到的应用类型为：{}",applyUrl,applyTypeCode);
                IamExceptionLoginStrategy exceptionLoginStrategy = iamExceptionLoginStrategyMapper.getApplyTypeCode(applyTypeCode);
                if (exceptionLoginStrategy == null){
                    throw new RuntimeException("该应用类型没有配置异常登录策略，请联系管理员配置");
                }
                //异常登录时间内是否允许登录
                isExceptionLoginTime = exceptionLoginStrategy.getIsExceptionLoginTime();
                //异常登录开始时间
                exceptionLoginStartTime = exceptionLoginStrategy.getExceptionLoginStartTime();
                //异常登录结束时间
                exceptionLoginEndTime = exceptionLoginStrategy.getExceptionLoginEndTime();
                //登录失败锁定时间
                Integer loginFailLockTime = exceptionLoginStrategy.getLoginFailLockTime();
                //登录失败几次锁定账号
                Integer loginFailFrequency = exceptionLoginStrategy.getLoginFailFrequency();
                //从缓存中获取失败次数
                Integer failures = RedisUtils.getObject(envName + accountKey, Integer.class);
                //如果失败次数大于策略管理设置的失败次数账号将会被锁定
                if(failures != null){
                    if (failures >= loginFailFrequency) {
                        //取最后一次失败时间
                        long errorTime = (long)RedisUtils.getObject(envName + ERROR_TIME+accountKey,long.class);
                        //获取系统时间
                        long nowTime = System.currentTimeMillis();
                        long time = (nowTime - errorTime) / (1000 * 60);
                        if (loginFailLockTime - time >= 0) {
                            throw new RuntimeException("登录失败次数过多，当前用户登陆已锁定，请" + (loginFailLockTime - time) + "分钟后重试");
                        }
                    }
                }
            }
            //判断用户名是否全部为数字并且是是十一位，如果是说明是手机号登录
            if (StringUtils.isNumeric(trim) && trim.length() == length){
                //判断第一个值是否为1
                String sOne = account.substring(0,1);
                if (!sOne.equals(PH)) {
                    return Result.failWithMsg("手机号格式错误");
                }
                // 手机号登录
                iamAccountBase.setPhone(account);
            } else {
                // 用户编码登录
                iamAccountBase.setEmployeeCode(account);
            }
            // 登录密码
            iamAccountBase.setPassword(passWord);
            accountBase = identityService.getAccountPassword(iamAccountBase);

            if(exceptionLoginStartTime != null && exceptionLoginEndTime!= null) {
                LocalDateTime now = LocalDateTime.now();
                ZoneId zoneId = ZoneId.systemDefault();
                LocalDateTime beginTime = LocalDateTime.ofInstant(exceptionLoginStartTime.toInstant(), zoneId);
                LocalDateTime endTime = LocalDateTime.ofInstant(exceptionLoginEndTime.toInstant(), zoneId);
                if (isExceptionLoginTime) {
                    if (now.isAfter(beginTime) && now.isBefore(endTime)) {
                        String ip = RequestUtils.getClientIp(request);
                        String userCode = accountBase.getUserCode();
                        if (accountBase != null) {
                            //记录异常登录日志
                            convertLoginLog(userCode,ip,Constants.LOG_LOGIN,true,true);
                        } else {
                            //如果返回值为空 则登录失败 记录异常登录失败日志
                            convertLoginLog(userCode,ip,Constants.LOG_LOGIN,true,false);
                        }
                    }
                } else {
                    if (now.isAfter(beginTime) && now.isBefore(endTime)) {
                        throw new RuntimeException("当前时间不允许登录,如有需要请联系管理员");
                    }
                }
            }
        }
        if (accountBase == null) {
            if (!Constants.USER_TYPE_ADMIN.equals(userType)) {
                //先根据账号key查询登录失败次数
                Integer i = (Integer) RedisUtils.getObject(envName + accountKey,Integer.class);
                if (i == null) {
                    i = 0;
                }
                //给失败次数加1并设置失效时间
                RedisUtils.setObject(envName + accountKey,i+1,3600, TimeUnit.MILLISECONDS);
                Date date = new Date();
                //存一个最后一次失败时间
                RedisUtils.setObject(envName + ERROR_TIME+accountKey,date.getTime());
            }
            return Result.failWithMsg("用户名或密码错误");
        }
        log.info("用户：{}登录成功", account);
        SessionInfo sessionInfo = new SessionInfo();
        BeanUtils.copyProperties(accountBase, sessionInfo);
        return Result.success(sessionInfo);
    }

    /**
     * 登出接口
     * @param request
     * @return
     */
    @Override
    public Result logout(HttpServletRequest request, HttpServletResponse response) {

        Cookie[] cookies = request.getCookies();
        String token = null;
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(Constants.HEADER_TOKEN)) {
                token = cookie.getValue();
            }
            cookie.setDomain(request.getHeader(Constants.X_APP_TYPE));
            cookie.setMaxAge(0);
            cookie.setValue(null);
            cookie.setPath("/");
            cookie.setHttpOnly(true);
            response.addCookie(cookie);
        }
        TokenInfo tokenInfo = TokenHelper.validateToken(token, jwtSecret);
        //如果token获取不到用户信息直接登出成功
        if (tokenInfo == null) {
            return Result.success("退出登录成功",null);
        }
        //删除token
        RedisUtils.del(envName + getRedisKey(tokenInfo.getUserCode(), tokenInfo.getIp(), tokenInfo.getToken()));
        //记录日志
        convertLoginLog(tokenInfo.getUserCode(), tokenInfo.getIp(),Constants.LOG_OUT,false,true);
        return Result.success("退出登录成功",null);
    }

    private void convertLoginLog (String userCode,String ip,String loginWay,Boolean isExLogin,Boolean status){
        IamLoginLog loginLog = new IamLoginLog();
        loginLog.setUserCode(userCode);
        IamAccountBase iamAccountBase = identityMapper.queryByUserCode(userCode,"");
        loginLog.setUserName(iamAccountBase.getName());
//       String mobileDevice = LoginUtil.getRequestHeader(request.getHeader("user-agent"));
        loginLog.setLoginWay(loginWay);
        loginLog.setLoginIp(ip);
        loginLog.setLoginTime(new Date());
        loginLog.setLoginStatus(status);
        loginLog.setIsExceptionLogin(isExLogin);
        userLogService.saveIamLoginLog(loginLog);
    }

    private String getRedisKey(String userCode, String ip, String token){
        return REDIS_PREFIX + ":PC:" + userCode + ":" + ip + ":" + com.jsbs.iam.common.core.utils.DigestUtils.md5Hex(token);
    }

    private String getAccountKey(String account){
        return REDIS_PREFIX + account;
    }

}
